/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.hilton.tigershark;

import java.io.File;
import java.util.ArrayList;
import java.util.Vector;

import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.http.SslError;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.HttpAuthHandler;
import android.webkit.SslErrorHandler;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.hilton.net.UrlUtils;
import com.hilton.tigershark.UriHandler.UrlData;
import com.hilton.tigershark.Utility.ClearThumbnails;
import com.hilton.tigershark.view.CustomizedWebview;
import com.hilton.tigershark.view.CustomizedWebviewFactory;
import com.hilton.tigershark.view.ErrorConsoleManager;
import com.hilton.tigershark.view.FindDialog;
import com.hilton.tigershark.view.HttpAuthenticationManager;
import com.hilton.tigershark.view.SSLDialogManager;
import com.hilton.tigershark.view.WebDialog;
import com.hilton.tigershark.view.WebviewFactory;

public class TabControl {
    final static int LOCK_ICON_UNSECURE = 0;
    final static int LOCK_ICON_SECURE   = 1;
    final static int LOCK_ICON_MIXED    = 2;
    private static final String LOGTAG = "TabControl";
    // Maximum number of tabs.
    private static final int MAX_TABS = 8;
    // Private array of WebViews that are used as tabs.
    private ArrayList<Tab> mTabs = new ArrayList<Tab>(MAX_TABS);
    // Queue of most recently viewed tabs.
    private ArrayList<Tab> mTabQueue = new ArrayList<Tab>(MAX_TABS);
    // Current position in mTabs.
    private int mCurrentTab = -1;
    // A private instance of BrowserActivity to interface with when adding and
    // switching between tabs.
    private final BrowserActivity mBrowserActivity;
    // Directory to store thumbnails for each WebView.
    private final File mThumbnailDir;
    private FrameLayout mContentView;
    // The up-to-date URL and title (these can be different from those stored
    // in WebView, since it takes some time for the information in WebView to
    // get updated)
    private String mUrl;
    private String mTitle;
    private Toast mStopToast;

    private Drawable    mMixLockIcon;
    private Drawable    mSecLockIcon;

    private FindDialog mFindDialog;
    private HttpAuthenticationManager mHttpAuthManager;
    private WebviewFactory mWebviewFactory;
    private ErrorConsoleManager mErrorConsoleManager;
    
    TabControl(BrowserActivity activity, FrameLayout container) {
        mBrowserActivity = activity;
        mContentView = container;
        mThumbnailDir = activity.getDir("thumbnails", 0);
        mSecLockIcon = Resources.getSystem().getDrawable(android.R.drawable.ic_secure);
        mMixLockIcon = Resources.getSystem().getDrawable(android.R.drawable.ic_partial_secure);
        mErrorConsoleManager = new ErrorConsoleManager(this, (LinearLayout) activity.findViewById(R.id.error_console));
        mWebviewFactory = new CustomizedWebviewFactory(activity);
    }

    void onStart(Bundle icicle, Intent intent) {
        if (!restoreState(icicle)) {
            // clear up the thumbnail directory if we can't restore the state as
            // none of the files in the directory are referenced any more.
            new ClearThumbnails().execute(getThumbnailDir().listFiles());
            // there is no quit on Android. But if we can't restore the state,
            // we can treat it as a new Browser, remove the old session cookies.
            CookieManager.getInstance().removeSessionCookie();
            final Bundle extra = intent.getExtras();
            // Create an initial tab.
            // If the intent is ACTION_VIEW and data is not null, the Browser is
            // invoked to view the content by another application. In this case,
            // the tab will be close when exit.
            UrlData urlData = Utility.getUrlDataFromIntent(intent);

            String action = intent.getAction();
            final Tab t = createNewTab(
                    (Intent.ACTION_VIEW.equals(action) &&
                            intent.getData() != null),
                            intent.getStringExtra(BrowserBridge.EXTRA_APPLICATION_ID), urlData.mUrl);
            setCurrentTab(t);
            attachTabToContentView(t);
            WebView webView = t.getWebView();
            if (extra != null) {
                int scale = extra.getInt(BrowserBridge.INITIAL_ZOOM_LEVEL, 0);
                if (scale > 0 && scale <= 1000) {
                    webView.setInitialScale(scale);
                }
            }

            if (urlData.isEmpty()) {
                loadUrl(webView, BrowserSettings.getInstance().getHomePage());
            } else {
                loadUrlDataIn(t, urlData);
            }
        } else {
            // TabControl.restoreState() will create a new tab even if
            // restoring the state fails.
            attachTabToContentView(getCurrentTab());
        }

        // Delete old thumbnails to save space
        File dir = getThumbnailDir();
        if (dir.exists()) {
            for (String child : dir.list()) {
                File f = new File(dir, child);
                f.delete();
            }
        }
    }

    /* package */ void goBackOnePageOrQuit() {
        Tab current = getCurrentTab();
        if (current == null) {
            /*
             * Instead of finishing the activity, simply push this to the back
             * of the stack and let ActivityManager to choose the foreground
             * activity. As BrowserActivity is singleTask, it will be always the
             * root of the task. So we can use either true or false for
             * moveTaskToBack().
             */
            mBrowserActivity.moveTaskToBack(true);
            return;
        }
        WebView w = current.getWebView();
        if (w.canGoBack()) {
            w.goBack();
        } else {
            // Check to see if we are closing a window that was created by
            // another window. If so, we switch back to that window.
            Tab parent = current.getParentTab();
            if (parent != null) {
                switchToTab(getTabIndex(parent));
                // Now we close the other tab
                closeTab(current);
            } else {
                if (current.closeOnExit()) {
                    // force the tab's inLoad() to be false as we are going to
                    // either finish the activity or remove the tab. This will
                    // ensure pauseWebViewTimers() taking action.
                    getCurrentTab().clearInLoad();
                    if (getTabCount() == 1) {
                        mBrowserActivity.finish();
                        return;
                    }
                    // call pauseWebViewTimers() now, we won't be able to call
                    // it in onPause() as the WebView won't be valid.
                    // Temporarily change mActivityInPause to be true as
                    // pauseWebViewTimers() will do nothing if mActivityInPause
                    // is false.
                    pauseWebViewTimers();
                    removeTabFromContentView(current);
                    removeTab(current);
                }
                /*
                 * Instead of finishing the activity, simply push this to the back
                 * of the stack and let ActivityManager to choose the foreground
                 * activity. As BrowserActivity is singleTask, it will be always the
                 * root of the task. So we can use either true or false for
                 * moveTaskToBack().
                 */
                mBrowserActivity.moveTaskToBack(true);
            }
        }
    }

    public void onPause() {
        pauseCurrentTab();
        cancelStopToast();
    }

    void resumeWebViewTimers() {
        Tab tab = getCurrentTab();
        if (tab == null) return; // monkey can trigger this
        CookieSyncManager.getInstance().startSync();
        WebView w = tab.getWebView();
        if (w != null) {
            w.resumeTimers();
        }
    }

    boolean pauseWebViewTimers() {
        Tab tab = getCurrentTab();
        boolean inLoad = tab.inLoad();
        if (!inLoad) {
            CookieSyncManager.getInstance().stopSync();
            WebView w = getCurrentWebView();
            if (w != null) {
                w.pauseTimers();
            }
            return true;
        } else {
            return false;
        }
    }

    void onConfigurationChanged(Configuration config) {
        if (mHttpAuthManager != null && mHttpAuthManager.hasDialogShowing()) {
            mHttpAuthManager.reshowDialog();
        }
        if (mSSLDialogManager != null && mSSLDialogManager.hasDialogShowing()) {
            mSSLDialogManager.reshowDialogs();
        }
    }
    
    File getThumbnailDir() {
        return mThumbnailDir;
    }

    // TODO: Only for BrowserSettings, should remove it in future, let BrowserSettings talks directly to Activity
    BrowserActivity getBrowserActivity() {
        return mBrowserActivity;
    }

    /**
     * Return the current tab's main WebView. This will always return the main
     * WebView for a given tab and not a subwindow.
     * @return The current tab's WebView.
     */
    WebView getCurrentWebView() {
        Tab t = getTab(mCurrentTab);
        if (t == null) {
            return null;
        }
        return t.getWebView();
    }

    /**
     * Return the current tab's top-level WebView. This can return a subwindow
     * if one exists.
     * @return The top-level WebView of the current tab.
     */
    WebView getCurrentTopWebView() {
        Tab t = getTab(mCurrentTab);
        if (t == null) {
            return null;
        }
        return t.getTopWindow();
    }

    /**
     * Return the current tab's subwindow if it exists.
     * @return The subwindow of the current tab or null if it doesn't exist.
     */
    WebView getCurrentSubWindow() {
        Tab t = getTab(mCurrentTab);
        if (t == null) {
            return null;
        }
        return t.getSubWebView();
    }

    /**
     * Return the tab at the specified index.
     * @return The Tab for the specified index or null if the tab does not
     *         exist.
     */
    Tab getTab(int index) {
        if (index >= 0 && index < mTabs.size()) {
            return mTabs.get(index);
        }
        return null;
    }

    /**
     * Return the current tab.
     * @return The current tab.
     */
    public Tab getCurrentTab() {
        return getTab(mCurrentTab);
    }

    /**
     * Return the current tab index.
     * @return The current tab index
     */
    int getCurrentIndex() {
        return mCurrentTab;
    }

    /**
     * Given a Tab, find it's index
     * @param Tab to find
     * @return index of Tab or -1 if not found
     */
    int getTabIndex(Tab tab) {
        if (tab == null) {
            return -1;
        }
        return mTabs.indexOf(tab);
    }

    boolean canCreateNewTab() {
        return MAX_TABS != mTabs.size();
    }

    /**
     * Create a new tab.
     * @return The newly createTab or null if we have reached the maximum
     *         number of open tabs.
     */
    Tab createNewTab(boolean closeOnExit, String appId, String url) {
        int size = mTabs.size();
        // Return false if we have maxed out on tabs
        if (MAX_TABS == size) {
            return null;
        }
        final WebView w = createNewWebView();

        // Create a new tab and add it to the tab list
        Tab t = new Tab(mBrowserActivity, this, w, closeOnExit, appId, url);
        mTabs.add(t);
        // Initially put the tab in the background.
        t.putInBackground();
        return t;
    }

    /**
     * Create a new tab with default values for closeOnExit(false),
     * appId(null), and url(null).
     */
    Tab createNewTab() {
        return createNewTab(false, null, null);
    }

    /**
     * Remove the parent child relationships from all tabs.
     */
    void removeParentChildRelationShips() {
        for (Tab tab : mTabs) {
            tab.removeFromTree();
        }
    }

    /**
     * Remove the tab from the list. If the tab is the current tab shown, the
     * last created tab will be shown.
     * @param t The tab to be removed.
     */
    boolean removeTab(Tab t) {
        if (t == null) {
            return false;
        }

        // Grab the current tab before modifying the list.
        Tab current = getCurrentTab();

        // Remove t from our list of tabs.
        mTabs.remove(t);

        // Put the tab in the background only if it is the current one.
        if (current == t) {
            t.putInBackground();
            mCurrentTab = -1;
        } else {
            // If a tab that is earlier in the list gets removed, the current
            // index no longer points to the correct tab.
            mCurrentTab = getTabIndex(current);
        }

        // destroy the tab
        t.destroy();
        // clear it's references to parent and children
        t.removeFromTree();

        // The tab indices have shifted, update all the saved state so we point
        // to the correct index.
        for (Tab tab : mTabs) {
            Vector<Tab> children = tab.getChildTabs();
            if (children != null) {
                for (Tab child : children) {
                    child.setParentTab(tab);
                }
            }
        }

        // Remove it from the queue of viewed tabs.
        mTabQueue.remove(t);
        return true;
    }

    /**
     * Destroy all the tabs and subwindows
     */
    void destroy() {
        for (Tab t : mTabs) {
            t.destroy();
        }
        mTabs.clear();
        mTabQueue.clear();
    }

    /**
     * Returns the number of tabs created.
     * @return The number of tabs created.
     */
    int getTabCount() {
        return mTabs.size();
    }


    /**
     * Save the state of all the Tabs.
     * @param outState The Bundle to save the state to.
     */
    void saveState(Bundle outState) {
        final int numTabs = getTabCount();
        outState.putInt(Tab.NUMTABS, numTabs);
        final int index = getCurrentIndex();
        outState.putInt(Tab.CURRTAB, (index >= 0 && index < numTabs) ? index : 0);
        for (int i = 0; i < numTabs; i++) {
            final Tab t = getTab(i);
            if (t.saveState()) {
                outState.putBundle(Tab.WEBVIEW + i, t.getSavedState());
            }
        }
    }

    /**
     * Restore the state of all the tabs.
     * @param inState The saved state of all the tabs.
     * @return True if there were previous tabs that were restored. False if
     *         there was no saved state or restoring the state failed.
     */
    boolean restoreState(Bundle inState) {
        final int numTabs = (inState == null)
        ? -1 : inState.getInt(Tab.NUMTABS, -1);
        if (numTabs == -1) {
            return false;
        } else {
            final int currentTab = inState.getInt(Tab.CURRTAB, -1);
            for (int i = 0; i < numTabs; i++) {
                if (i == currentTab) {
                    Tab t = createNewTab();
                    // Me must set the current tab before restoring the state
                    // so that all the client classes are set.
                    setCurrentTab(t);
                    if (!t.restoreState(inState.getBundle(Tab.WEBVIEW + i))) {
                        Log.w(LOGTAG, "Fail in restoreState, load home page.");
                        t.getWebView().loadUrl(BrowserSettings.getInstance()
                                .getHomePage());
                    }
                } else {
                    // Create a new tab and don't restore the state yet, add it
                    // to the tab list
                    Tab t = new Tab(mBrowserActivity, null, null, false, null, null);
                    Bundle state = inState.getBundle(Tab.WEBVIEW + i);
                    if (state != null) {
                        t.setSavedState(state);
                        t.populatePickerDataFromSavedState();
                        // Need to maintain the app id and original url so we
                        // can possibly reuse this tab.
                        t.setAppId(state.getString(Tab.APPID));
                        t.setOriginalUrl(state.getString(Tab.ORIGINALURL));
                    }
                    mTabs.add(t);
                    // added the tab to the front as they are not current
                    mTabQueue.add(0, t);
                }
            }
            // Rebuild the tree of tabs. Do this after all tabs have been
            // created/restored so that the parent tab exists.
            for (int i = 0; i < numTabs; i++) {
                final Bundle b = inState.getBundle(Tab.WEBVIEW + i);
                final Tab t = getTab(i);
                if (b != null && t != null) {
                    final int parentIndex = b.getInt(Tab.PARENTTAB, -1);
                    if (parentIndex != -1) {
                        final Tab parent = getTab(parentIndex);
                        if (parent != null) {
                            parent.addChildTab(t);
                        }
                    }
                }
            }
        }
        return true;
    }

    /**
     * Free the memory in this order, 1) free the background tabs; 2) free the
     * WebView cache;
     */
    void freeMemory() {
        if (getTabCount() == 0) return;

        // free the least frequently used background tabs
        Vector<Tab> tabs = getHalfLeastUsedTabs(getCurrentTab());
        if (tabs.size() > 0) {
            Log.w(LOGTAG, "Free " + tabs.size() + " tabs in the browser");
            for (Tab t : tabs) {
                // store the WebView's state.
                t.saveState();
                // destroy the tab
                t.destroy();
            }
            return;
        }

        // free the WebView's unused memory (this includes the cache)
        Log.w(LOGTAG, "Free WebView's unused memory and cache");
        WebView view = getCurrentWebView();
        if (view != null) {
            view.freeMemory();
        }
    }

    private Vector<Tab> getHalfLeastUsedTabs(Tab current) {
        Vector<Tab> tabsToGo = new Vector<Tab>();

        // Don't do anything if we only have 1 tab or if the current tab is
        // null.
        if (getTabCount() == 1 || current == null) {
            return tabsToGo;
        }

        if (mTabQueue.size() == 0) {
            return tabsToGo;
        }

        // Rip through the queue starting at the beginning and tear down half of
        // available tabs which are not the current tab or the parent of the
        // current tab.
        int openTabCount = 0;
        for (Tab t : mTabQueue) {
            if (t != null && t.getWebView() != null) {
                openTabCount++;
                if (t != current && t != current.getParentTab()) {
                    tabsToGo.add(t);
                }
            }
        }

        openTabCount /= 2;
        if (tabsToGo.size() > openTabCount) {
            tabsToGo.setSize(openTabCount);
        }

        return tabsToGo;
    }

    /**
     * Show the tab that contains the given WebView.
     * @param view The WebView used to find the tab.
     */
    Tab getTabFromView(WebView view) {
        final int size = getTabCount();
        for (int i = 0; i < size; i++) {
            final Tab t = getTab(i);
            if (t.getSubWebView() == view || t.getWebView() == view) {
                return t;
            }
        }
        return null;
    }

    /**
     * Return the tab with the matching application id.
     * @param id The application identifier.
     */
    Tab getTabFromId(String id) {
        if (id == null) {
            return null;
        }
        final int size = getTabCount();
        for (int i = 0; i < size; i++) {
            final Tab t = getTab(i);
            if (id.equals(t.getAppId())) {
                return t;
            }
        }
        return null;
    }

    /**
     * Stop loading in all opened WebView including subWindows.
     */
    void stopAllLoading() {
        final int size = getTabCount();
        for (int i = 0; i < size; i++) {
            final Tab t = getTab(i);
            final WebView webview = t.getWebView();
            if (webview != null) {
                webview.stopLoading();
            }
            final WebView subview = t.getSubWebView();
            if (subview != null) {
                webview.stopLoading();
            }
        }
    }

    // This method checks if a non-app tab (one created within the browser)
    // matches the given url.
    private boolean tabMatchesUrl(Tab t, String url) {
        if (t.getAppId() != null) {
            return false;
        }
        WebView webview = t.getWebView();
        if (webview == null) {
            return false;
        } else if (url.equals(webview.getUrl())
                || url.equals(webview.getOriginalUrl())) {
            return true;
        }
        return false;
    }

    /**
     * Return the tab that has no app id associated with it and the url of the
     * tab matches the given url.
     * @param url The url to search for.
     */
    Tab findUnusedTabWithUrl(String url) {
        if (url == null) {
            return null;
        }
        // Check the current tab first.
        Tab t = getCurrentTab();
        if (t != null && tabMatchesUrl(t, url)) {
            return t;
        }
        // Now check all the rest.
        final int size = getTabCount();
        for (int i = 0; i < size; i++) {
            t = getTab(i);
            if (tabMatchesUrl(t, url)) {
                return t;
            }
        }
        return null;
    }

    /**
     * Recreate the main WebView of the given tab. Returns true if the WebView
     * requires a load, whether it was due to the fact that it was deleted, or
     * it is because it was a voice search.
     */
    boolean recreateWebView(Tab t, UriHandler.UrlData urlData) {
        final String url = urlData.mUrl;
        final WebView w = t.getWebView();
        if (w != null) {
            if (url != null && url.equals(t.getOriginalUrl())
                    // Treat a voice intent as though it is a different URL,
                    // since it most likely is.
                    && urlData.mVoiceIntent == null) {
                // The original url matches the current url. Just go back to the
                // first history item so we can load it faster than if we
                // rebuilt the WebView.
                final WebBackForwardList list = w.copyBackForwardList();
                if (list != null) {
                    w.goBackOrForward(-list.getCurrentIndex());
                    w.clearHistory(); // maintains the current page.
                    return false;
                }
            }
            t.destroy();
        }
        // Create a new WebView. If this tab is the current tab, we need to put
        // back all the clients so force it to be the current tab.
        t.setWebView(createNewWebView());
        if (getCurrentTab() == t) {
            setCurrentTab(t, true);
        }
        // Clear the saved state and picker data
        t.setSavedState(null);
        t.clearPickerData();
        // Save the new url in order to avoid deleting the WebView.
        t.setOriginalUrl(url);
        return true;
    }

    /**
     * Creates a new WebView and registers it with the global settings.
     */
    private WebView createNewWebView() {
        return mWebviewFactory.createWebview();
    }

    /**
     * Put the current tab in the background and set newTab as the current tab.
     * @param newTab The new tab. If newTab is null, the current tab is not
     *               set.
     */
    boolean setCurrentTab(Tab newTab) {
        return setCurrentTab(newTab, false);
    }

    void pauseCurrentTab() {
        Tab t = getCurrentTab();
        if (t != null) {
            t.pause();
        }
    }

    void resumeCurrentTab() {
        Tab t = getCurrentTab();
        if (t != null) {
            t.resume();
        }
    }

    /**
     * If force is true, this method skips the check for newTab == current.
     */
    private boolean setCurrentTab(Tab newTab, boolean force) {
        Tab current = getTab(mCurrentTab);
        if (current == newTab && !force) {
            return true;
        }
        if (current != null) {
            current.putInBackground();
            mCurrentTab = -1;
        }
        if (newTab == null) {
            return false;
        }

        // Move the newTab to the end of the queue
        int index = mTabQueue.indexOf(newTab);
        if (index != -1) {
            mTabQueue.remove(index);
        }
        mTabQueue.add(newTab);

        // Display the new current tab
        mCurrentTab = mTabs.indexOf(newTab);
        WebView mainView = newTab.getWebView();
        boolean needRestore = (mainView == null);
        if (needRestore) {
            // Same work as in createNewTab() except don't do new Tab()
            mainView = createNewWebView();
            newTab.setWebView(mainView);
        }
        newTab.putInForeground();
        if (needRestore) {
            // Have to finish setCurrentTab work before calling restoreState
            if (!newTab.restoreState(newTab.getSavedState())) {
                mainView.loadUrl(BrowserSettings.getInstance().getHomePage());
            }
        }
        return true;
    }

    // Tab manipulations 
    // Attach the given tab to the content view.
    // this should only be called for the current tab.
    void attachTabToContentView(Tab t) {
        // Attach the container that contains the main WebView and any other UI
        // associated with the tab.
        t.attachTabToContentView(mContentView);

        if (shouldShowErrorConsole()) {
            mErrorConsoleManager.attachErrorConsole(t.getErrorConsole());
        }

        WebView view = t.getWebView();
        mBrowserActivity.attachTitlebarTo((CustomizedWebview) t.getTopWindow());
        // Request focus on the top window.
        t.getTopWindow().requestFocus();
        mBrowserActivity.attachPieControl();
    }

    // Attach a sub window to the main WebView of the given tab.
    void attachSubWindow(Tab t) {
        t.attachSubWindow(mContentView);
        getTopWindow().requestFocus();
    }

    // Remove the given tab from the content view.
    void removeTabFromContentView(Tab t) {
        // Remove the container that contains the main WebView.
        mBrowserActivity.detachPieControl();
        mBrowserActivity.attachTitlebarTo(null);
        t.removeTabFromContentView(mContentView);
        mErrorConsoleManager.detachErrorConsole(t.getErrorConsole());
    }

    void dismissSubWindow() {
        final Tab t = getCurrentTab();
        if (t != null) {
            dismissSubWindow(t);
        }
    }
    
    // Remove the sub window if it exists. Also called by TabControl when the
    // user clicks the 'X' to dismiss a sub window.
    /* package */ void dismissSubWindow(Tab t) {
        t.removeSubWindow(mContentView);
        // dismiss the subwindow. This will destroy the WebView.
        t.dismissSubWindow();
        getTopWindow().requestFocus();
    }

    /**
     * Switch tabs.  Called by the TitleBarSet when sliding the title bar
     * results in changing tabs.
     * @param index Index of the tab to change to, as defined by
     *              getTabIndex(Tab t).
     * @return boolean True if we successfully switched to a different tab.  If
     *                 the indexth tab is null, or if that tab is the same as
     *                 the current one, return false.
     */
    /* package */ boolean switchToTab(int index) {
        Tab tab = getTab(index);
        Tab currentTab = getCurrentTab();
        if (tab == null || tab == currentTab) {
            return false;
        }
        if (currentTab != null) {
            // currentTab may be null if it was just removed.  In that case,
            // we do not need to remove it
            removeTabFromContentView(currentTab);
        }
        setCurrentTab(tab);
        attachTabToContentView(tab);
        resetTitleIconAndProgress();
        updateLockIconToLatest();
        return true;
    }

    /* package */ Tab openTabToHomePage() {
        return openTabAndShow(BrowserSettings.getInstance().getHomePage(), false, null);
    }

    /* package */ void closeCurrentWindow() {
        final Tab current = getCurrentTab();
        if (getTabCount() == 1) {
            // This is the last tab.  Open a new one, with the home
            // page and close the current one.
            openTabToHomePage();
            closeTab(current);
            return;
        }
        final Tab parent = current.getParentTab();
        int indexToShow = -1;
        if (parent != null) {
            indexToShow = getTabIndex(parent);
        } else {
            final int currentIndex = getCurrentIndex();
            // Try to move to the tab to the right
            indexToShow = currentIndex + 1;
            if (indexToShow > getTabCount() - 1) {
                // Try to move to the tab to the left
                indexToShow = currentIndex - 1;
            }
        }
        if (switchToTab(indexToShow)) {
            // Close window
            closeTab(current);
        }
    }
    // A wrapper function of {@link #openTabAndShow(UrlData, boolean, String)}
    // that accepts url as string.
    Tab openTabAndShow(String url, boolean closeOnExit, String appId) {
        return openTabAndShow(new UrlData(url), closeOnExit, appId);
    }

    // This method does a ton of stuff. It will attempt to create a new tab
    // if we haven't reached MAX_TABS. Otherwise it uses the current tab. If
    // url isn't null, it will load the given url.
    /* package */Tab openTabAndShow(UrlData urlData, boolean closeOnExit,
            String appId) {
        Tab currentTab = getCurrentTab();
        if (!canCreateNewTab()) {
            Tab closeTab = getTab(0);
            closeTab(closeTab);
            if (closeTab == currentTab) {
                currentTab = null;
            }
        }
        final Tab tab = createNewTab(closeOnExit, appId,
                urlData.mUrl);
        WebView webview = tab.getWebView();
        // If the last tab was removed from the active tabs page, currentTab
        // will be null.
        if (currentTab != null) {
            removeTabFromContentView(currentTab);
        }
        // We must set the new tab as the current tab to reflect the old
        // animation behavior.
        setCurrentTab(tab);
        attachTabToContentView(tab);
        if (!urlData.isEmpty()) {
            loadUrlDataIn(tab, urlData);
        }
        return tab;
    }

    Tab openTab(String url) {
        if (BrowserSettings.getInstance().openInBackground()) {
            Tab t = createNewTab();
            if (t != null) {
                WebView view = t.getWebView();
                loadUrl(view, url);
            }
            return t;
        } else {
            return openTabAndShow(url, false, null);
        }
    }
    /**
     * Close the tab, remove its associated title bar, and adjust mTabControl's
     * current tab to a valid value.
     */
    /* package */ void closeTab(Tab t) {
        int currentIndex = getCurrentIndex();
        int removeIndex = getTabIndex(t);
        removeTab(t);
        if (currentIndex >= removeIndex && currentIndex != 0) {
            currentIndex--;
        }
        setCurrentTab(getTab(currentIndex));
        resetTitleIconAndProgress();
    }

    WebView getTopWindow() {
        return getCurrentTopWebView();
    }

    /**
     * load the url in current webview in current tab.
     */
    public void loadUrl(final String url) {
        loadUrl(getTopWindow(), url);
    }

    // Called when loading from context menu or LOAD_URL message
    void loadUrlFromContext(String url) {
        // In case the user enters nothing.
        if (url != null && url.length() != 0) {
            url = UrlUtils.smartUrlFilter(url);
            //            if (!view.getWebViewClient().shouldOverrideUrlLoading(view, url)) {
            loadUrl(getTopWindow(), url);
            //            }/
        }
    }

    /**
     * Load the URL into the given WebView and update the title bar
     * to reflect the new load.  Call this instead of WebView.loadUrl
     * directly.
     * @param view The WebView used to load url.
     * @param url The URL to load.
     */
    void loadUrl(WebView view, String url) {
        updateTitleBarForNewLoad(view, url);
        view.loadUrl(url);
    }

    /**
     * Load UrlData into a Tab and update the title bar to reflect the new
     * load.  Call this instead of UrlData.loadIn directly.
     * @param t The Tab used to load.
     * @param data The UrlData being loaded.
     */
    void loadUrlDataIn(Tab t, UrlData data) {
        updateTitleBarForNewLoad(t.getWebView(), data.mUrl);
        data.loadIn(t);
    }

    /**
     * If the WebView is the top window, update the title bar to reflect
     * loading the new URL.  i.e. set its text, clear the favicon (which
     * will be set once the page begins loading), and set the progress to
     * INITIAL_PROGRESS to show that the page has begun to load. Called
     * by loadUrl and loadUrlDataIn.
     * @param view The WebView that is starting a load.
     * @param url The URL that is being loaded.
     */
    private void updateTitleBarForNewLoad(WebView view, String url) {
        if (view == getTopWindow()) {
            setUrlTitle(url, null);
            setFavicon(null);
            //            onProgressChanged(view, BrowserActivity.INITIAL_PROGRESS);
        }
    }

    // Reset the title and the icon based on the given item.
    void resetTitleAndIcon(WebView view) {
        WebHistoryItem item = view.copyBackForwardList().getCurrentItem();
        if (item != null) {
            setUrlTitle(item.getUrl(), item.getTitle());
            setFavicon(item.getFavicon());
        } else {
            setUrlTitle(null, null);
            setFavicon(null);
        }
    }

    /**
     * Sets a title composed of the URL and the title string.
     * @param url The URL of the site being loaded.
     * @param title The title of the site being loaded.
     */
    void setUrlTitle(String url, String title) {
        mUrl = url;
        mTitle = title;

        // If we are in voice search mode, the title has already been set.
        final String t = TextUtils.isEmpty(title) ? url : title;
        mBrowserActivity.setDisplayTitle(t);
    }
    /**
     * Resets the browser title-view to whatever it must be
     * (for example, if we had a loading error)
     * When we have a new page, we call resetTitle, when we
     * have to reset the titlebar to whatever it used to be
     * (for example, if the user chose to stop loading), we
     * call resetTitleAndRevertLockIcon.
     */
    public /* package */ void resetTitleAndRevertLockIcon() {
        getCurrentTab().revertLockIcon();
        updateLockIconToLatest();
        resetTitleIconAndProgress();
    }

    /**
     * Reset the title, favicon, and progress.
     */
    private void resetTitleIconAndProgress() {
        WebView current = getCurrentWebView();
        if (current == null) {
            return;
        }
        resetTitleAndIcon(current);
        int progress = current.getProgress();
    }
    // Set the favicon in the title bar.
    void setFavicon(Bitmap icon) {
        mBrowserActivity.setFavicon(icon);
    }

    /**
     * Resets the lock icon. This method is called when we start a new load and
     * know the url to be loaded.
     */
    void resetLockIcon(String url) {
        // Save the lock-icon state (we revert to it if the load gets cancelled)
        getCurrentTab().resetLockIcon(url);
        updateLockIconImage(LOCK_ICON_UNSECURE);
    }

    /**
     * Update the lock icon to correspond to our latest state.
     */
    void updateLockIconToLatest() {
        updateLockIconImage(getCurrentTab().getLockIconType());
    }

    /**
     * Updates the lock-icon image in the title-bar.
     */
    private void updateLockIconImage(int lockIconType) {
        Drawable d = null;
        if (lockIconType == LOCK_ICON_SECURE) {
            d = mSecLockIcon;
        } else if (lockIconType == LOCK_ICON_MIXED) {
            d = mMixLockIcon;
        }
        mBrowserActivity.toggleLockIcon(d);
    }

    
    void showErrorConsole() {
	mErrorConsoleManager.showErrorConsole();
    }

    void hideErrorConsole() {
	mErrorConsoleManager.hideErrorConsole();
    }
    
    boolean shouldShowErrorConsole() {
        return mErrorConsoleManager.shouldShowErrorConsole();
    }

    /* package */ public void stopLoading() {
        mDidUserStopLoading = true;
        resetTitleAndRevertLockIcon();
        WebView w = getTopWindow();
        w.stopLoading();
        mBrowserActivity.onPageFinished(w, w.getUrl());
        cancelStopToast();
        mStopToast = Toast.makeText(mBrowserActivity, R.string.stopping, Toast.LENGTH_SHORT);
        mStopToast.show();
    }
    private boolean mDidUserStopLoading;
    private SSLDialogManager mSSLDialogManager;
    boolean didUserStopLoading() {
        return mDidUserStopLoading;
    }

    void cancelStopToast() {
        if (mStopToast != null) {
            mStopToast.cancel();
            mStopToast = null;
        }
    }
    
    /**
     * Set HTTP authentication password.
     *
     * @param host The host for the password
     * @param realm The realm for the password
     * @param username The username for the password. If it is null, it means
     *            password can't be saved.
     * @param password The password
     */
    public void setHttpAuthUsernamePassword(String host, String realm,
            String username, String password) {
        WebView w = getTopWindow();
        if (w != null) {
            w.setHttpAuthUsernamePassword(host, realm, username, password);
        }
    }

    private WebView showDialog(WebDialog dialog) {
        mBrowserActivity.hideTitlebar();
        final Tab tab = getCurrentTab();
        return tab.showDialog(dialog);
    }

    public boolean dialogsUp() {
        return null != mFindDialog && mFindDialog.isVisible();
    }

    private boolean closeDialog(WebDialog dialog) {
        if (null == dialog || !dialog.isVisible()) return false;
        getCurrentTab().closeDialog(dialog);
        dialog.dismiss();
        return true;
    }

    public void closeDialogs() {
        closeDialog(mFindDialog);
        mBrowserActivity.showTitlebar();
    }

    public void showFindDialog() {
        if (null == mFindDialog) {
            mFindDialog = new FindDialog(mBrowserActivity);
        }
        showDialog(mFindDialog);
    }

    public void onResume() {
        resumeCurrentTab();
        resumeWebViewTimers();
    }

    public void onDestroy() {
        // Remove the current tab and sub window
        Tab t = getCurrentTab();
        if (t != null) {
            dismissSubWindow(t);
            removeTabFromContentView(t);
        }
        // Destroy all the tabs
        destroy();
    }

    public void requestFocus() {
        getTopWindow().requestFocus();
    }

    public String getWebpageTitle() {
        return getTopWindow().getTitle();
    }

    public String getWebpageUrl() {
        return getTopWindow().getUrl();
    }

    public void debugDump() {
        getTopWindow().debugDump();
    }

    public void zoomIn() {
        getTopWindow().zoomIn();
    }

    public void zoomOut() {
        getTopWindow().zoomOut();
    }

    public boolean hasTopWindow() {
        return getTopWindow() != null;
    }

    public void reload() {
        getTopWindow().reload();
    }

    public void goBack() {
        getTopWindow().goBack();
    }

    public void goForward() {
        getTopWindow().goForward();
    }

    public void pageUp(boolean b) {
        getTopWindow().pageUp(b);
    }

    public void pageDown(boolean b) {
        getTopWindow().pageDown(b);
    }

    void showHttpAuthDialog(HttpAuthHandler handler, String host, String realm) {
        if (mHttpAuthManager == null) {
            mHttpAuthManager = new HttpAuthenticationManager(mBrowserActivity, this);
        }
        mHttpAuthManager.showHttpAuthDialog(handler, host, realm);
    }

    void showSSLCertificateDialog(WebView view, SslErrorHandler handler,
            SslError error) {
        if (mSSLDialogManager == null) {
            mSSLDialogManager = new SSLDialogManager(mBrowserActivity, this);
        }
        mSSLDialogManager.showSSLCertificateDialog(handler, error);
    }
}